﻿using UnityEngine;
using System.Collections.Generic;
using System.Text;

namespace GameBasic.IO
{
    /// <summary>
    /// UTF-8-BOM
    /// </summary>
    public struct CsvData
    {
        public static readonly string SPLIT_LINE = "\r\n";// TODO: it's depend on the source file
        public static readonly char UTF8_BOM = '\uFEFF';

        // header
        public string[] columnName;
        public List<List<string>> rows;

        public int ColCount { get { return columnName.Length; } }
        public int RowCount { get { return rows.Count; } }

        public CsvData(string data, bool hasHead = true)
        {
            // spilt into rows
            string[] strArray = data.Split(SPLIT_LINE, System.StringSplitOptions.RemoveEmptyEntries);
            // first row is column name
            columnName = strArray[0].Trim(UTF8_BOM).Split(',');

            // row shifting
            int rowShift = hasHead ? 1 : 0;
            // row count, is last row empty
            int rowCount = strArray[^1] == "" ? strArray.Length - 1 - rowShift : strArray.Length - rowShift;

            // store row data
            rows = new List<List<string>>(rowCount);
            for (int i = 0; i < rowCount; i++)
                rows.Add(GetColumns(strArray[i + rowShift]));
        }

        /// <summary>
        /// Split row into columns
        /// </summary>
        private List<string> GetColumns(string text)
        {
            // commonSpliter = true: 逗号是分割符
            // commonSpliter = false: 遇到下一个引号前，逗号","表示文本而非分割符。
            bool commonSpliter = true;

            List<string> result = new List<string>(ColCount);
            int startIndex = 0;

            int lastIndex = text.Length - 1;
            for (int i = 0, len = text.Length; i < len; i++)
            {
                bool isFirst = i == 0;
                bool isLast = i == lastIndex;
                //
                char c = text[i];
                bool isCommon = c == ',';
                bool isQuot = c == '"';

                // first char is column is null
                if (isFirst && isCommon)
                {
                    result.Add("");
                    startIndex = i + 1;
                    continue;
                }

                // quot
                commonSpliter = isQuot ? !commonSpliter : commonSpliter;
                if (commonSpliter)
                {
                    //last 
                    if (isLast)
                    {
                        if (isCommon)
                        {
                            result.Add(text.Substring(startIndex, i - startIndex));
                            result.Add("");
                        }
                        else
                        {
                            result.Add(text.Substring(startIndex, i - startIndex + 1));
                        }
                        break;
                    }
                    else
                    {
                        // common
                        if (isCommon)
                        {
                            // add column
                            result.Add(text.Substring(startIndex, i - startIndex));
                            startIndex = i + 1;
                            continue;
                        }
                    }
                }
                // last && the last char is a "
                else if (isLast)
                {
                    result.Add(text.Substring(startIndex, i - startIndex + 1));
                    Debug.LogError("CSV format error: " + text);
                    break;
                }
            }

            // replace "" with "
            for (int i = 0, len = result.Count; i < len; i++)
            {
                string t = result[i];
                if (t.Length >= 2)
                    result[i] = CheckQuotation(t);
            }

            return result;
        }

        /// <summary>
        /// 处理引号
        /// </summary>
        private string CheckQuotation(string str)
        {
            // remove first and last
            if (str[0] == '"' && str[str.Length - 1] == '"')
                str = str.Substring(1, str.Length - 2);

            // replace double "" with single "
            str = str.Replace("\"\"", "\"");

            return str;
        }

        public T Parse<T>(int row, StringBuilder sb = null) where T : new()
        {
            //sb ??= new StringBuilder(ColCount * 8);
            //return JsonUtility.FromJson<T>(ToJson(row, sb).ToString());
            return ParseImpl1<T>(row);
        }

        public T[] ParseAll<T>(StringBuilder sb = null) where T : new()
        {
            return ParseImpl1<T>();
        }

        public JsonArray<T> ParseArray<T>() where T : new()
        {
            return JsonUtility.FromJson<JsonArray<T>>(ToJsonArray());
        }

        T ParseImpl1<T>(int i) where T : new()
        {
            int colCount = ColCount;

            // Fields
            var fields = typeof(T).GetFields().ToDictionary(f => f.Name);

            List<string> row = rows[i];
            object t = new T();
            for (int j = 0; j < colCount; j++)
            {
                if (fields.TryGetValue(columnName[j], out var fi))
                    fi.SetValue(t, row[j]);
            }

            return (T)t;
        }

        /// <summary>
        /// Parse use reflection
        /// </summary>
        T[] ParseImpl1<T>() where T : new()
        {
            int rowCount = RowCount;
            int colCount = ColCount;

            // Results
            T[] result = new T[rowCount];

            // Fields
            var fields = typeof(T).GetFields().ToDictionary(f => f.Name);

            for (int i = 0; i < rowCount; i++)
            {
                List<string> row = rows[i];

                T t = new();
                var tRef = __makeref(t);
                for (int j = 0; j < colCount; j++)
                {
                    if (fields.TryGetValue(columnName[j], out var fi))
                    {
                        string colText = row[j];

                        switch (fi.FieldType.Name)
                        {
                            case nameof(System.Int32):
                                {
                                    TextUtil.TryParse(colText, out int v);
                                    fi.SetValueDirect(tRef, v);
                                    break;
                                }
                            case nameof(System.Single):
                                {
                                    TextUtil.TryParse(colText, out float v);
                                    fi.SetValueDirect(tRef, v);
                                    break;
                                }
                            case nameof(System.Boolean):
                                {
                                    bool.TryParse(colText, out bool v);
                                    fi.SetValueDirect(tRef, v);
                                    break;
                                }
                            default:
                                try
                                {
                                    fi.SetValueDirect(tRef, colText);
                                }
                                catch { }
                                break;
                        }
                    }
                }

                result[i] = t;
            }

            return result;
        }

        T[] ParseImpl2<T>(StringBuilder sb = null)
        {
            sb ??= new StringBuilder(ColCount * 8);

            T[] results = new T[RowCount];
            for (int i = 0, length = RowCount; i < length; i++)
            {
                results[i] = JsonUtility.FromJson<T>(ToJson(i, sb).ToString());
                sb.Clear();
            }
            return results;
        }

        /// <summary>
        /// Return a <see cref="JsonArray{T}"/> json string for all rows
        /// </summary>
        /// <returns></returns>
        public string ToJsonArray()
        {
            StringBuilder sb = new StringBuilder(ColCount * 8);
            sb.Append("{\"").Append(JsonArray<object>.ARRAY_NAME).Append("\":[");

            for (int i = 0, len = rows.Count; i < len; i++)
            {
                if (i > 0)
                    sb.Append(",");
                ToJson(i, sb);
            }
            sb.Append("]}");
            return sb.ToString();
        }

        /// <summary>
        /// Parse given row data to json string
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="row"></param>
        StringBuilder ToJson(int row, StringBuilder sb)
        {
            sb.Append("{");
            for (int i = 0, len = rows[row].Count; i < len; i++)
            {
                string column = rows[row][i];
                if (i > 0)
                    sb.Append(",");
                sb.Append("\"").Append(columnName[i]).Append("\":");

                // number?
                if (TextUtil.TryParse(column, out float number))
                {
                    sb.Append(TextUtil.ToString(number));
                }
                // text
                else
                {
                    column = column.Replace("\"", "\\\"").Replace("\n", "\\n");
                    sb.Append("\"").Append(column).Append("\"");
                }

            }
            sb.Append("}");
            return sb;
        }

        public void Foreach(RectInt rect, System.Action<Vector2Int, int, string> act)
        {
            int index = 0;
            for (int r = rect.y; r < rect.height; r++)
            {
                var row = rows[r];
                for (int c = rect.x; c < rect.width; c++)
                {
                    act(new Vector2Int(c, r), index, row[c]);
                    index++;
                }
            }
        }

        static string ToCsvText<T>(List<T> list)
        {
            var type = typeof(T);
            var fields = type.GetFields();

            StringBuilder sb = new StringBuilder();
            sb.Append(UTF8_BOM);
            // head
            for (int i = 0, length = fields.Length; i < length; i++)
            {
                if (i > 0)
                    sb.Append(',');

                var field = fields[i];
                sb.Append(field.Name);
            }

            // rows
            sb.Append(SPLIT_LINE);
            for (int i = 0, length = list.Count; i < length; i++)
            {
                T t = list[i];
                for (int c = 0, col = fields.Length; c < col; c++)
                {
                    if (c > 0)
                        sb.Append(',');

                    var field = fields[c];
                    var value = field.GetValue(t);
                    sb.Append(value);
                }
                sb.Append(SPLIT_LINE);
            }

            return sb.ToString();
        }

        #region BenchMark
        public static void CsvBenchMark<T>(CsvData csv) where T : new()
        {
            int rep = 100;

            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < rep; i++)
            {
                csv.ParseImpl1<T>();
            }
            var t1 = stopwatch.ElapsedTicks;

            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < rep; i++)
            {
                csv.ParseImpl2<T>();
            }
            var t2 = stopwatch.ElapsedTicks;

            Debug.Log(string.Format("Reflection:{0} Json:{1}", t1, t2));
        }
        #endregion
        
    }
}
